using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Oracle.ManagedDataAccess.Client;
using Oracle.ManagedDataAccess.Types;
using OracleInternal.I18N;

namespace OracleInternal.Sharding
{
	internal class OracleShardingKeyImpl : IComparable<OracleShardingKeyImpl>
	{
		private class Encoder
		{
			private static int TNS_HEADER_VERSION = 1;

			private string headerVersionTypeStr;

			internal Encoder()
			{
				headerVersionTypeStr = TNS_HEADER_VERSION + " " + 0;
			}

			internal string encodeKey(OracleShardingKeyImpl shardKeyToEncode)
			{
				if (shardKeyToEncode == null)
				{
					return string.Empty;
				}
				if (shardKeyToEncode.m_subKeys.Count == 0)
				{
					return string.Empty;
				}
				string text = headerVersionTypeStr;
				for (int i = 0; i < shardKeyToEncode.m_subKeys.Count; i++)
				{
					text = text + " " + getInternalType((OracleDbType)shardKeyToEncode.m_oracleDbType[i]);
				}
				for (int j = 0; j < shardKeyToEncode.m_subKeys.Count; j++)
				{
					text = text + "," + Convert.ToBase64String((byte[])shardKeyToEncode.m_subKeys[j]);
				}
				return text;
			}
		}

		private class Decoder
		{
			private class ShardingKeyReader
			{
				private byte[] m_data;

				private int m_position;

				private static int KDKLBLEN = 128;

				private static int KDKLNULL = 255;

				public bool HasKeys
				{
					get;
					set;
				}

				public ShardingKeyReader(Stream stream)
				{
					if (HasKeys = stream != null)
					{
						m_data = new byte[stream.Length];
						stream.Read(m_data, 0, (int)stream.Length);
						stream.Dispose();
						m_position = 0;
					}
				}

				public int ReadByte()
				{
					if (m_position < m_data.Length)
					{
						return m_data[m_position++];
					}
					return -1;
				}

				private byte[] Read(int len)
				{
					byte[] array = new byte[len];
					Buffer.BlockCopy(m_data, m_position, array, 0, len);
					m_position += len;
					return array;
				}

				public List<byte[]> GetKeys()
				{
					if (!HasKeys)
					{
						return null;
					}
					List<byte[]> list = new List<byte[]>();
					byte[] array = null;
					while ((array = Next()) != null)
					{
						list.Add(array);
					}
					HasKeys = false;
					return list;
				}

				private byte[] Next()
				{
					try
					{
						int num = ReadByte();
						if (num < 0)
						{
							HasKeys = false;
							return null;
						}
						if (num == KDKLNULL)
						{
							num = 0;
						}
						else if (num >= KDKLBLEN)
						{
							num = (num - KDKLBLEN << 8) + ReadByte();
						}
						return Read(num);
					}
					catch (IOException)
					{
						HasKeys = false;
						return null;
					}
				}
			}

			private ShardingMetadata dbMetadata;

			internal Decoder(ShardingMetadata metadata)
			{
				if (metadata != null)
				{
					dbMetadata = metadata;
					return;
				}
				throw new ArgumentNullException("metadata", OracleStringResourceManager.GetErrorMesg(ResourceStringConstants.SHARDING_INVALID_TOPOLOGY));
			}

			internal List<OracleShardingKeyImpl> DecodeKey(Stream inStream, bool isSuperShardingKey, bool isHighKey)
			{
				List<byte[]> keys = new ShardingKeyReader(inStream).GetKeys();
				return BuildShardKeys(keys, isSuperShardingKey, isHighKey);
			}

			internal List<OracleShardingKeyImpl> BuildShardKeys(List<byte[]> subKeysByteArr, bool isSuperShardingKey, bool isHighKey)
			{
				List<OracleShardingKeyImpl> list = new List<OracleShardingKeyImpl>();
				List<SubKeyMetadata> list2 = (isSuperShardingKey ? dbMetadata.SuperShardingKeyColumns : dbMetadata.ShardingKeyColumns);
				ShardingType shardingType = (isSuperShardingKey ? dbMetadata.SuperShardingType : dbMetadata.ShardingType);
				if (subKeysByteArr == null || subKeysByteArr.Count <= 0)
				{
					throw new ArgumentNullException("subKeysByteArr", OracleStringResourceManager.GetErrorMesg(ResourceStringConstants.SHARDING_INVALID_TOPOLOGY));
				}
				if (list2 == null || list2.Count <= 0)
				{
					throw new InvalidOperationException(OracleStringResourceManager.GetErrorMesg(ResourceStringConstants.SHARDING_INVALID_TOPOLOGY));
				}
				switch (shardingType)
				{
				case ShardingType.HASH:
				{
					OracleShardingKeyImpl oracleShardingKeyImpl3 = null;
					byte[] array2 = subKeysByteArr[0];
					if (array2.Length != 0)
					{
						oracleShardingKeyImpl3 = new OracleShardingKeyImpl();
						oracleShardingKeyImpl3.SetShardingKey(OracleDbType.Decimal, array2);
						oracleShardingKeyImpl3.ShardKeyOraHash = new OracleDecimal(array2, bContainsLength: false).ToInt64();
						oracleShardingKeyImpl3.Build();
					}
					if (oracleShardingKeyImpl3 != null)
					{
						oracleShardingKeyImpl3.IsSuperShardingKey = isSuperShardingKey;
						list.Add(oracleShardingKeyImpl3);
					}
					return list;
				}
				case ShardingType.RANGE:
				{
					if (subKeysByteArr.Count != list2.Count)
					{
						throw new InvalidOperationException(OracleStringResourceManager.GetErrorMesg(ResourceStringConstants.SHARDING_INVALID_TOPOLOGY));
					}
					IEnumerator<byte[]> enumerator3 = subKeysByteArr.GetEnumerator();
					bool flag2 = enumerator3.MoveNext();
					if (flag2)
					{
						OracleShardingKeyImpl oracleShardingKeyImpl2 = new OracleShardingKeyImpl();
						IEnumerator<SubKeyMetadata> enumerator4 = list2.GetEnumerator();
						while (enumerator4.MoveNext())
						{
							if (!flag2)
							{
								return null;
							}
							byte[] array = DecodeInStandardCharset(enumerator3.Current, enumerator4.Current.CharSet, enumerator4.Current.DataType);
							if (array.Length != 0)
							{
								oracleShardingKeyImpl2.SetShardingKey(enumerator4.Current.DataType, array);
							}
							else
							{
								oracleShardingKeyImpl2.SetShardingKey(enumerator4.Current.DataType, isHighKey ? NULL_SHARD_KEY_HIGH : NULL_SHARD_KEY_LOW);
							}
							flag2 = enumerator3.MoveNext();
						}
						oracleShardingKeyImpl2.Build();
						oracleShardingKeyImpl2.IsSuperShardingKey = isSuperShardingKey;
						list.Add(oracleShardingKeyImpl2);
					}
					return list;
				}
				case ShardingType.LIST:
				{
					IEnumerator<byte[]> enumerator = subKeysByteArr.GetEnumerator();
					bool flag = enumerator.MoveNext();
					while (flag)
					{
						OracleShardingKeyImpl oracleShardingKeyImpl = new OracleShardingKeyImpl();
						IEnumerator<SubKeyMetadata> enumerator2 = list2.GetEnumerator();
						while (enumerator2.MoveNext())
						{
							if (!flag)
							{
								return null;
							}
							byte[] data = DecodeInStandardCharset(enumerator.Current, enumerator2.Current.CharSet, enumerator2.Current.DataType);
							oracleShardingKeyImpl.SetShardingKey(enumerator2.Current.DataType, data);
							flag = enumerator.MoveNext();
						}
						oracleShardingKeyImpl.Build();
						oracleShardingKeyImpl.IsSuperShardingKey = isSuperShardingKey;
						list.Add(oracleShardingKeyImpl);
					}
					return list;
				}
				default:
					return null;
				}
			}
		}

		internal class NullShardingKeyType
		{
			internal bool isHighKey
			{
				get;
				set;
			}

			internal NullShardingKeyType(bool isHighKey)
			{
				this.isHighKey = isHighKey;
			}

			public override int GetHashCode()
			{
				return "NULL".GetHashCode();
			}

			public override string ToString()
			{
				if (!isHighKey)
				{
					return "MIN";
				}
				return "MAX";
			}
		}

		private static int DEFAULT_CHAR_CHARSET = 873;

		private static int DEFAULT_NCHAR_CHARSET = 2000;

		internal static long DEFAULT_ORA_HASH = -1L;

		private static long ORA_HASH_MAX_VALUE = 4294967296L;

		internal int m_hashCode;

		internal ArrayList m_data;

		internal ArrayList m_oracleDbType;

		internal ArrayList m_subKeys;

		internal ArrayList m_defaultSubKeyMetadata;

		internal long m_shardKeyOraHash;

		private static NullShardingKeyType NULL_SHARD_KEY_LOW = new NullShardingKeyType(isHighKey: false);

		private static NullShardingKeyType NULL_SHARD_KEY_HIGH = new NullShardingKeyType(isHighKey: true);

		internal long ShardKeyOraHash
		{
			get
			{
				return m_shardKeyOraHash;
			}
			set
			{
				m_shardKeyOraHash = value;
			}
		}

		internal bool IsSuperShardingKey
		{
			get;
			set;
		}

		internal void Init()
		{
			m_hashCode = base.GetHashCode();
			m_data = new ArrayList();
			m_oracleDbType = new ArrayList();
			m_subKeys = new ArrayList();
			m_defaultSubKeyMetadata = new ArrayList();
			m_shardKeyOraHash = DEFAULT_ORA_HASH;
			IsSuperShardingKey = false;
		}

		internal OracleShardingKeyImpl()
		{
			Init();
		}

		internal void SetShardingKey(OracleDbType oracleDbType, object data)
		{
			m_oracleDbType.Add(oracleDbType);
			m_data.Add(data);
		}

		internal void Build()
		{
			int num = 1;
			m_subKeys.Clear();
			for (int i = 0; i < m_data.Count; i++)
			{
				int defaultSubKeyCharSet = getDefaultSubKeyCharSet((OracleDbType)m_oracleDbType[i]);
				m_defaultSubKeyMetadata.Add(new SubKeyMetadata(i, (OracleDbType)m_oracleDbType[i], defaultSubKeyCharSet));
				if (m_data[i] is NullShardingKeyType)
				{
					m_subKeys.Add(new byte[0]);
					num = 31 * num + m_data[i]!.GetHashCode();
					continue;
				}
				Conv targetExtra = null;
				if (-1 != defaultSubKeyCharSet)
				{
					targetExtra = Conv.GetInstance(defaultSubKeyCharSet);
				}
				Type subKeyDataClass = getSubKeyDataClass((OracleDbType)m_oracleDbType[i]);
				byte[] array = DotnetToDotnetConverter.convert(m_data[i], subKeyDataClass, null, null, targetExtra);
				m_subKeys.Add(array);
				num = 31 * num + ((array != null) ? GetBytesHashCode(array) : 0);
			}
			m_hashCode = num;
		}

		internal bool IsValid(ShardingMetadata shardingMetadata)
		{
			List<SubKeyMetadata> list = (IsSuperShardingKey ? shardingMetadata.SuperShardingKeyColumns : shardingMetadata.ShardingKeyColumns);
			if (list == null)
			{
				return false;
			}
			if (list.Count != m_subKeys.Count)
			{
				return false;
			}
			for (int i = 0; i < list.Count; i++)
			{
				Type subKeyDataClass = getSubKeyDataClass(list[i].DataType);
				if (!getSubKeyDataClass((OracleDbType)m_oracleDbType[i]).Equals(subKeyDataClass))
				{
					return false;
				}
			}
			return true;
		}

		internal long ComputeShardKeyOraHash(ShardingMetadata metadata)
		{
			if (metadata == null)
			{
				return -1L;
			}
			if (m_shardKeyOraHash != DEFAULT_ORA_HASH)
			{
				return m_shardKeyOraHash;
			}
			List<SubKeyMetadata> list = (IsSuperShardingKey ? metadata.SuperShardingKeyColumns : metadata.ShardingKeyColumns);
			long num = 0L;
			for (int i = 0; i < m_subKeys.Count; i++)
			{
				if (m_subKeys[i] != null)
				{
					long num2 = (uint)KggHashGenerator.hash(EncodeInDBCharset(m_subKeys[i] as byte[], (OracleDbType)m_oracleDbType[i], list[i].CharSet));
					num += num2;
				}
			}
			m_shardKeyOraHash = num % ORA_HASH_MAX_VALUE;
			return m_shardKeyOraHash;
		}

		public override int GetHashCode()
		{
			return m_hashCode;
		}

		private int GetBytesHashCode(byte[] data)
		{
			int num = 1;
			if (data != null && data.Length != 0)
			{
				for (int i = 0; i < data.Length; i++)
				{
					num = 31 * num + data[i];
				}
			}
			return num;
		}

		public override bool Equals(object obj)
		{
			OracleShardingKeyImpl oracleShardingKeyImpl = obj as OracleShardingKeyImpl;
			if (oracleShardingKeyImpl == null)
			{
				return false;
			}
			if (m_subKeys.Count != oracleShardingKeyImpl.m_subKeys.Count)
			{
				return false;
			}
			for (int i = 0; i < m_subKeys.Count; i++)
			{
				byte[] m = m_subKeys[i] as byte[];
				byte[] n = oracleShardingKeyImpl.m_subKeys[i] as byte[];
				if (CompareBytes(m, n) != 0)
				{
					return false;
				}
			}
			return true;
		}

		int IComparable<OracleShardingKeyImpl>.CompareTo(OracleShardingKeyImpl other)
		{
			if (other == null)
			{
				throw new ArgumentException();
			}
			if (m_shardKeyOraHash != DEFAULT_ORA_HASH && other.m_shardKeyOraHash != DEFAULT_ORA_HASH)
			{
				if (m_shardKeyOraHash >= other.m_shardKeyOraHash)
				{
					if (m_shardKeyOraHash != other.m_shardKeyOraHash)
					{
						return 1;
					}
					return 0;
				}
				return -1;
			}
			if (m_subKeys.Count != other.m_subKeys.Count)
			{
				throw new ArgumentException(OracleStringResourceManager.GetErrorMesg(ResourceStringConstants.SHARDING_INVALID_KEY_TYPE));
			}
			for (int i = 0; i < m_subKeys.Count; i++)
			{
				byte[] m = m_subKeys[i] as byte[];
				byte[] n = other.m_subKeys[i] as byte[];
				if (m_data[i] is NullShardingKeyType && other.m_data[i] is NullShardingKeyType)
				{
					continue;
				}
				if (m_data[i] is NullShardingKeyType)
				{
					if (((NullShardingKeyType)m_data[i]).isHighKey)
					{
						return 1;
					}
					return -1;
				}
				if (other.m_data[i] is NullShardingKeyType)
				{
					if (((NullShardingKeyType)other.m_data[i]).isHighKey)
					{
						return -1;
					}
					return 1;
				}
				int num = CompareBytes(m, n);
				if (num != 0)
				{
					return num;
				}
			}
			return 0;
		}

		private int CompareBytes(byte[] m, byte[] n)
		{
			int num = m.Length;
			int num2 = n.Length;
			int i = 0;
			int num3 = Math.Min(num, num2);
			int num4 = 0;
			int num5 = 0;
			for (; i < num3; i++)
			{
				num4 = m[i] & 0xFF;
				num5 = n[i] & 0xFF;
				if (num4 != num5)
				{
					if (num4 < num5)
					{
						return -1;
					}
					return 1;
				}
			}
			if (num == num2)
			{
				return 0;
			}
			if (num > num2)
			{
				return 1;
			}
			return -1;
		}

		public override string ToString()
		{
			if (DEFAULT_ORA_HASH != m_shardKeyOraHash)
			{
				return m_shardKeyOraHash.ToString();
			}
			if (m_subKeys.Count != m_oracleDbType.Count)
			{
				throw new InvalidOperationException();
			}
			string text = string.Empty;
			for (int i = 0; i < m_subKeys.Count; i++)
			{
				string text2 = null;
				if (m_data[i] is NullShardingKeyType)
				{
					text2 = m_data[i]!.ToString();
				}
				else
				{
					OracleDbType oracleDbType = (OracleDbType)m_oracleDbType[i];
					byte[] array = m_subKeys[i] as byte[];
					if (OracleDbType.Char == oracleDbType || OracleDbType.Varchar2 == oracleDbType || OracleDbType.NChar == oracleDbType || OracleDbType.NVarchar2 == oracleDbType)
					{
						text2 = Conv.GetInstance(getDefaultSubKeyCharSet(oracleDbType)).ConvertBytesToString(array, 0, array.Length);
					}
					else if (OracleDbType.Decimal == oracleDbType)
					{
						text2 = new OracleDecimal(array, bContainsLength: false).ToString();
					}
				}
				if (!string.IsNullOrEmpty(text2))
				{
					text = ((!string.IsNullOrEmpty(text)) ? (text + "," + text2) : text2);
				}
			}
			return text;
		}

		internal static int getInternalType(OracleDbType dataType)
		{
			int num = 0;
			switch (dataType)
			{
			case OracleDbType.NVarchar2:
			case OracleDbType.Varchar2:
				return 1;
			case OracleDbType.Char:
			case OracleDbType.NChar:
				return 96;
			case OracleDbType.Double:
			case OracleDbType.Int16:
			case OracleDbType.Int32:
			case OracleDbType.Int64:
			case OracleDbType.Single:
				return 2;
			case OracleDbType.Byte:
				return 2;
			case OracleDbType.Decimal:
				return 2;
			case OracleDbType.Date:
				return 12;
			case OracleDbType.TimeStamp:
				return 180;
			case OracleDbType.Raw:
				return 23;
			default:
				throw new ArgumentException(OracleStringResourceManager.GetErrorMesg(ResourceStringConstants.SHARDING_UNSUPPORTED_KEY_TYPE));
			}
		}

		internal static OracleDbType getExternalType(int dataType)
		{
			return dataType switch
			{
				1 => OracleDbType.Varchar2, 
				96 => OracleDbType.Char, 
				2 => OracleDbType.Decimal, 
				12 => OracleDbType.Date, 
				23 => OracleDbType.Raw, 
				180 => OracleDbType.TimeStamp, 
				_ => throw new ArgumentException(OracleStringResourceManager.GetErrorMesg(ResourceStringConstants.SHARDING_UNSUPPORTED_KEY_TYPE)), 
			};
		}

		private static int getDefaultSubKeyCharSet(OracleDbType dataType)
		{
			switch (dataType)
			{
			case OracleDbType.Char:
			case OracleDbType.Varchar2:
				return DEFAULT_CHAR_CHARSET;
			case OracleDbType.NChar:
			case OracleDbType.NVarchar2:
				return DEFAULT_NCHAR_CHARSET;
			default:
				return -1;
			}
		}

		private static byte[] EncodeInDBCharset(byte[] subKey, OracleDbType oracleDbType, int dbCharset)
		{
			if (OracleDbType.Char == oracleDbType || OracleDbType.Varchar2 == oracleDbType || OracleDbType.NChar == oracleDbType || OracleDbType.NVarchar2 == oracleDbType)
			{
				string text = Conv.GetInstance(getDefaultSubKeyCharSet(oracleDbType)).ConvertBytesToString(subKey, 0, subKey.Length);
				return Conv.GetInstance(dbCharset).ConvertStringToBytes(text, 0, text.Length);
			}
			return subKey;
		}

		private static byte[] DecodeInStandardCharset(byte[] dbEncodedKey, int dbCharSet, OracleDbType dbDataType)
		{
			if (dbEncodedKey == null || dbEncodedKey.Length == 0)
			{
				return new byte[0];
			}
			if (OracleDbType.Char == dbDataType || OracleDbType.Varchar2 == dbDataType || OracleDbType.NChar == dbDataType || OracleDbType.NVarchar2 == dbDataType)
			{
				string text = Conv.GetInstance(dbCharSet).ConvertBytesToString(dbEncodedKey, 0, dbEncodedKey.Length);
				return Conv.GetInstance(getDefaultSubKeyCharSet(dbDataType)).ConvertStringToBytes(text, 0, text.Length);
			}
			return dbEncodedKey;
		}

		private static Type getSubKeyDataClass(OracleDbType dataType)
		{
			Type type = null;
			switch (dataType)
			{
			case OracleDbType.Char:
			case OracleDbType.Varchar2:
				return typeof(OracleString);
			case OracleDbType.NChar:
			case OracleDbType.NVarchar2:
				return typeof(OracleString);
			case OracleDbType.Int16:
			case OracleDbType.Int32:
			case OracleDbType.Int64:
				return typeof(OracleDecimal);
			case OracleDbType.Double:
			case OracleDbType.Single:
				return typeof(OracleDecimal);
			case OracleDbType.Raw:
				return typeof(OracleBinary);
			case OracleDbType.Byte:
				return typeof(OracleDecimal);
			case OracleDbType.Decimal:
				return typeof(OracleDecimal);
			case OracleDbType.Date:
				return typeof(OracleDate);
			case OracleDbType.TimeStamp:
				return typeof(OracleTimeStamp);
			default:
				throw new ArgumentException(OracleStringResourceManager.GetErrorMesg(ResourceStringConstants.SHARDING_UNSUPPORTED_KEY_TYPE));
			}
		}

		internal string EncodeKeyinB64Format()
		{
			return new Encoder().encodeKey(this);
		}

		internal static List<OracleShardingKeyImpl> DecodeKeys(Stream keysData, ShardingMetadata metadata, bool isSuperKey, bool isKeyHigh)
		{
			return new Decoder(metadata).DecodeKey(keysData, isSuperKey, isKeyHigh);
		}

		internal static List<OracleShardingKeyImpl> DecodeKeys(List<byte[]> subKeysByteArr, ShardingMetadata metadata, bool isSuperKey, bool isKeyHigh)
		{
			return new Decoder(metadata).BuildShardKeys(subKeysByteArr, isSuperKey, isKeyHigh);
		}
	}
}
